package cn.cloud.project.minio.controller;

import cn.cloud.project.minio.pojo.*;
import cn.cloud.project.minio.template.MinioTemplate;
import cn.cloud.project.minio.template.PearMinioTemplate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.minio.CreateMultipartUploadResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import io.minio.messages.Part;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: cql
 * @date: 2024/12/3
 * @description:
 */
@Slf4j
@RestController
@RequestMapping("/oss")
public class OSSController {

    @Resource
    private MinioTemplate minioTemplate;

    @Resource
    private PearMinioTemplate pearMinioTemplate;


    @PostMapping("/upload")
    @ResponseBody
    public Object upload(MultipartFile file, String bucketName) throws IOException {
        return minioTemplate.putObject(file.getInputStream(), bucketName, file.getOriginalFilename());
    }


    @PostMapping("/download")
    public void download(String bucketName, String fileName, HttpServletResponse response) {
        try (InputStream inputStream = minioTemplate.getObject(bucketName, fileName)) {
            fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
            ServletOutputStream outputStream = response.getOutputStream();
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("utf-8");
            response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
            byte[] bytes = new byte[1024];
            int len;
            while ((len = inputStream.read(bytes)) > 0) {
                outputStream.write(bytes, 0, len);
            }
            outputStream.close();
        } catch (Exception e) {
            log.error("file download from minio exception, file name: {}", fileName, e);
        }
    }

    /**
     * 服务端签名直传
     */
    @PostMapping("/presign/form")
    public Map<String, String> presignForm(@RequestBody PresignParam presignParam) {
        try {
            // 生成凭证并返回
            return minioTemplate.getPresignedPostFormData(presignParam.getBucketName(), presignParam.getFileName(), presignParam.getType());
        } catch (Exception e) {
            log.error("file presign from minio exception, file name: {}", presignParam.getFileName(), e);
        }
        return new HashMap<>();
    }

    /**
     * 服务端签名直传
     */
    @GetMapping("/presign/url")
    public String presignUrl(PresignParam presignParam) {
        try {
            // 生成一个7天有效的预签名URL
            return minioTemplate.getPresignedObjectUrl(presignParam.getBucketName(), presignParam.getFileName(), Method.PUT);
        } catch (Exception e) {
            log.error("file presign from minio exception, file name: {}", presignParam.getFileName(), e);
        }
        return null;
    }


    /**
     * 获取临时访问地址，通过get的形式访问
     */
    @GetMapping("/get/url")
    public String getUrl(PresignParam presignParam) {
        try {
            // 生成一个7天有效的预签名URL
            return minioTemplate.getPresignedObjectUrl(presignParam.getBucketName(), presignParam.getFileName(), Method.GET);
        } catch (Exception e) {
            log.error("file presign from minio exception, file name: {}", presignParam.getFileName(), e);
        }
        return null;
    }

    /**
     * 获取分片上传每一片上传的地址
     * 分片上传:
     * https://juejin.cn/post/7312176750591623220
     * https://juejin.cn/post/7213275960900632632
     * 如果是高版本：https://blog.csdn.net/qq_34813456/article/details/135111891
     */
    @GetMapping("/multipart/url")
    public MinioPearVo multipartUploadUrl(@RequestParam(value = "chunkNum", defaultValue = "0") Integer chunkNum,
                                          @RequestParam("fileName") String fileName,
                                          @RequestParam("contentType") String contentType) throws Exception {

        Multimap<String, String> headerMap = HashMultimap.create();
        // headerMap.put("Content-Type", contentType);

        CreateMultipartUploadResponse uploadResponse = pearMinioTemplate
                .createMultipartUploadAsync("demo", null, fileName, headerMap, null)
                .get();




        Map<String, String> reqParams = new HashMap<>(2);
        reqParams.put("uploadId", uploadResponse.result().uploadId());
        MinioPearVo pearVo = new MinioPearVo();
        pearVo.setUploadId(uploadResponse.result().uploadId());
        List<MinioPearVo.PearUploadData> uploads = new ArrayList<>();
        for (int i = 1; i <= chunkNum; i++) {
            reqParams.put("partNumber", String.valueOf(i));
            String objectUrl = pearMinioTemplate.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket("demo")
                            .object(fileName)
                            .expiry(1, TimeUnit.DAYS)
                            .extraQueryParams(reqParams)
                            .build());
            MinioPearVo.PearUploadData uploadData = new MinioPearVo.PearUploadData();
            uploadData.setUploadUrl(objectUrl).setParkNum(i);
            uploads.add(uploadData);
        }
        pearVo.setParts(uploads);
        return pearVo;
    }


    /**
     * 分片合并
     */
    @PostMapping("/multipart/complete")
    public String completeMultipart(@RequestBody CompleteMultipartUploadRequest request) throws Exception {

        // MinIO要求在完成上传时，所有分片必须按照其编号从小到大依次列出。
        List<Part> completedParts = request.getEtags().stream()
                .sorted(Comparator.comparing(Etag::getPartNumber))
                .map(etag -> new Part(etag.getPartNumber(), etag.getTag()))
                .collect(Collectors.toList());
        pearMinioTemplate.completeMultipartUploadAsync("demo", null, request.getFileName(),
                request.getUploadId(), completedParts.toArray(new Part[]{}), null, null);
        return "上传成功";
    }

    /**
     * 结合MD5判断文件是否存在
     */
    @PostMapping("/check")
    public Boolean checkFileExists(@RequestBody FileCheckRequest request) {
        String realFileName = request.getMd5() + "_" + request.getFileName();
        StatObjectResponse stat = minioTemplate.statObject("demo", realFileName);
        return stat != null;
    }


    /**
     * 断点续传：
     * https://blog.csdn.net/scholar_yao/article/details/136709055
     * https://www.cnblogs.com/jsonq/p/18186340
     * https://github.com/robinsyn/MinIO_Demo
     */


}